package com.bozer.springsecuritydemo.config;

import com.bozer.springsecuritydemo.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.Producer;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Collections;
import java.util.Properties;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private UserService userService;

    @Bean
    public PasswordEncoder passwordEncoder() {
//        return new BCryptPasswordEncoder();
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * 重写了WebSecurityConfigurerAdapter#authenticationManager()方法后不会走此方法
     *
     * @param auth
     * @throws Exception
     */
    /*@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }*/
    @Bean
    public MyAuthenticationProvider myAuthenticationProvider() {
        MyAuthenticationProvider provider = new MyAuthenticationProvider();
        provider.setUserDetailsService(userService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return new ProviderManager(Collections.singletonList(myAuthenticationProvider()));
    }

    /**
     * 配置角色前缀,去除前缀
     *
     * @return GrantedAuthorityDefaults
     */
    @Bean
    public GrantedAuthorityDefaults grantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
    }

    /**
     * 配置角色继承
     *
     * @return RoleHierarchy
     */
    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        //admin>user admin角色拥有user角色的权限
        roleHierarchy.setHierarchy("admin > user");
        return roleHierarchy;
    }

    /**
     * 使用默认的JdbcUserDetailsManager
     *
     * @param web
     * @throws Exception
     */
    /*@Autowired
    private DataSource dataSource;

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        //基于内存的UserDetailsManager
//        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        //基于数据库的UserDetailsManager
        JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
        if (!manager.userExists("user")) {
            //如果用户不存在就创建
            manager.createUser(User.withUsername("user").password("123").roles("user").build());
        }
        if (!manager.userExists("admin")) {
            manager.createUser(User.withUsername("admin").password("123").roles("admin").build());
        }
        return manager;
    }*/
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**", "/css/**", "/images/**", "/verifyCode/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //使用添加过滤器的方式处理验证码登录
//        http.addFilterBefore(new VerifyCodeFilter(objectMapper), UsernamePasswordAuthenticationFilter.class);
        http.authorizeRequests()
                .antMatchers("/user/**").access("hasRole('user')")
                .antMatchers("/admin/**").access("hasRole('admin')")
//                .antMatchers("/user/**").hasRole("user")
//                .antMatchers("/admin/**").hasRole("admin")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                //登录页面
                .loginPage("/login.html")
                //登录接口
                .loginProcessingUrl("/login")
//                .defaultSuccessUrl("/s")
                //登录成功处理
                .successHandler((request, response, authentication) -> {
                    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    objectMapper.writeValue(response.getWriter(), authentication.getPrincipal());
                })
                //登录失败处理 用户名错误,密码错误,禁用都会走此处理
                .failureHandler((request, response, exception) -> {
                    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    objectMapper.writeValue(response.getWriter(), exception.getMessage());
                })
                .permitAll()
                .and()
                .exceptionHandling()
                //未认证处理
                .authenticationEntryPoint((request, response, authException) -> {
                    //AuthenticationEntryPoint
                    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    objectMapper.writeValue(response.getWriter(), authException.getMessage());
                })
                //权限不足异常处理
                .accessDeniedHandler((request, response, accessDeniedException) -> {
                    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    objectMapper.writeValue(response.getWriter(), accessDeniedException.getMessage());
                })

                .and()
                .logout()
                //注销成功处理
                .logoutSuccessHandler((request, response, authentication) -> {
                    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    objectMapper.writeValue(response.getWriter(), "注销成功");
                })
                .and()
                .csrf().disable();
    }

    /**
     * 图片验证码
     *
     * @return Producer
     */
    @Bean
    public Producer verifyCode() {
        Properties properties = new Properties();
        properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "150");
        properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "50");
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789");
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

    /**
     * 自定义登录处理器,重写additionalAuthenticationChecks方法,添加验证码登录检验
     */
    @Slf4j
    static class MyAuthenticationProvider extends DaoAuthenticationProvider {
        @Override
        protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
            /*HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String code = request.getParameter("code");
            if (code == null || !code.equalsIgnoreCase((String) request.getSession().getAttribute(Constants.VERIFY_CODE_NAME))) {
                throw new AuthenticationServiceException("验证码输入有误");
            }
            request.getSession().removeAttribute(Constants.VERIFY_CODE_NAME);
            log.debug("验证码校验通过");*/
            //调用父类校验
            super.additionalAuthenticationChecks(userDetails, authentication);
        }
    }

}
